%!TEX encoding = UTF-8 Unicode
\chapter{Dalvik虚拟机}
\label{Chap:dalvik}
Dalvik是专为Android设计的一种类JVM虚拟机，是Android应用程序的实际运行平台。从恶意代码分析的角度，Dalvik上DEX文件格式的重要性，相当于Windows上PE文件格式；Dalvik指令集的重要性，相当于PC上x86指令集的重要性。在静态分析中，分析人员有一半以上的时间是在与Dalvik指令打交道；在动态分析中，绝大部分时候没有恶意代码源码，也会涉及Dalvik级的调试；如果要将一些分析工作自动化，或展开一些研究，就不得不进行DEX格式的解析。

\section{设计思想}
JVM是一个已经设计成熟并被实践检验过的成熟的虚拟机。但在Android这样的移动平台，存在CPU性能较低、可用内存极少、电力由电池供应、没有交换空间等限制，直接使用JVM运行复杂的应用程序较为困难。此外，使用JVM还存在许可的问题。因此，Dan Bornstein设计并实现了Dalvik虚拟（名字来源于冰岛的一个小渔村）。

从虚拟级别上来说，Dalvik是一个进程级的虚拟机，提供普通应用程序的运行环境，像Java语言的JVM、.NET平台的CLR一样。从体系结构来看，Dalvik是一个基于寄存器的虚拟机。

与JVM这种基于栈的虚拟机相比，基于寄存器的虚拟机在对相同应用程序的执行上快三分之一，还能从流水线型处理器获得额外的性能提升，并有更快的加载速度和更好的容错性\cite{dalvik_analysis}。

Dalvik运行于其底层的Linux进程中，执行DEX格式文件中的Dalvik指令。通过一些库（zlib、Java核心库、openssl等）实现功能和对系统的调用，通过JNI规范调用本地代码执行。Dalvik利用Linux进程的用户ID来管理文件访问权限，并实现内存隔离，通过Linux进程间通信在应用程序之间共享数据。
\section{DEX文件格式}
\subsection{类型描述符}
\label{SubSec:dalvik_data_type}
DEX格式中，使用缩略符号表示数据类型，如下表所示：

\begin{table}[htbp]
  \caption{Dalvik数据类型描述符}
  \label{Fig:dalvik_type}
  \centering
  \begin{tabular}{ll}
    \toprule
    \textbf{句法} & \textbf{含义} \\
    \midrule
    \lstinline!V! & \lstinline!void!，只能用于返回值 \\
    \lstinline!Z! & \lstinline!boolean! \\
    \lstinline!B! & \lstinline!byte! \\
    \lstinline!S! & \lstinline!short! \\
    \lstinline!C! & \lstinline!char! \\
    \lstinline!I! & \lstinline!int! \\
    \lstinline!J! & \lstinline!long! \\
    \lstinline!F! & \lstinline!float! \\
    \lstinline!D! & \lstinline!double! \\
    \lstinline!Lfully/qualified/Name;! & \lstinline!fully/qualified/Name!类 \\
    \lstinline![descriptor! & \lstinline!descriptor!类型的数组 \\
    \bottomrule
  \end{tabular}
\end{table}

例如，\lstinline![Ljava/lang/String;!表示\lstinline!String[]!。

\section{ODEX文件格式}
\label{Sec:odex}

\section{指令集}
关于Dalvik指令集的主要参考文档是Android源码的dalvik/docs/dalvik-bytecode.html文件。本节的主要内容来自于该文档。

指令集的设计是一门艺术。从分析人员的角度考虑，不需要了解其中诸多细节。下面是指令集中为了便于人阅读的一些语法规则：
\begin{itemize}
  \item 两个操作数，前面是目的寄存器，后面是源寄存器
  \item 一般类型的64位opcode在32位opcode基础上加\lstinline!-wide!后缀；特殊类型opcode在一般类型opcode基础上加类型后缀，例如\lstinline!-char!、\lstinline!-int!、\lstinline!-long!、\lstinline!-float!、\lstinline!-string!、\lstinline!-object!等
  \item 对不是32位或64位操作的opcode，还加上特定后缀以说明其操作位数，例如\lstinline!/8!表示8位操作，\lstinline!/16!表示16位操作
\end{itemize}
例如，对指令\lstinline!move-wide/from16 vAA, vBBBB!，其中字段含义如下：
\begin{itemize}
  \item[move] 基本的opcode，说明指令功能
  \item[wide] 名称后缀，说明它操作64位数据
  \item[from16] 指令后缀，说明它的源操作数是一个16位寄存器
  \item[vAA] 目的寄存器，\lstinline!AA!是两个字母，表示寄存器的范围是\lstinline!v0!到\lstinline!v255!（一个字母表示4位）
  \item[vBBBB] 源寄存器，\lstinline!BBBB!是四个字母，表示寄存器的范围是\lstinline!v0!到\lstinline!v65535!
\end{itemize}
下面我们介绍Dalvik指令集，限于篇幅只列举和说明基本opcode，关于每个opcode可以适用的名称后缀、指令后缀、寄存器范围等，请参考dalvik/docs/dalvik-bytecode.html文件。
\begin{filecontents}{chapter-cn/LTXtable-dalvikopcode.tmp}
  \begin{longtable}{lX}
    \hline \textbf{基本指令} & \textbf{描述} \\
    \hline \lstinline!nop! & 空指令 \\
    \hline \lstinline!move! & 将源寄存器的值拷贝给目标寄存器 \\
    \hline \lstinline!move-result! & 将最近一次\lstinline!invoke!类型指令（函数调用）所返回的结果拷贝给指定的寄存器 \\
    \hline \lstinline!move-exception! & 将最近一次捕获到的异常值拷贝给指定寄存器 \\
    \hline \lstinline!return-void! & 在\lstinline!void!型的方法中返回，类似于C中的直接\lstinline!return! \\
    \hline \lstinline!return! & 在方法中，以指定寄存器的值返回 \\
    \hline \lstinline!const! & 将一个常量值（或常量字符串）赋给指定的寄存器 \\
    \hline \lstinline!monitor-enter! & 请求对指定目标的监视器 \\
    \hline \lstinline!monitor-exit! & 释放对指定目标的监视器 \\
    \hline \lstinline!check-cast! & 如果寄存器不能被动态转换为指定类型，就释放一个\lstinline!ClassCastException!异常 \\
    \hline \lstinline!instance-of! & 检查源寄存器中引用的值是否是指定类型数据，将结果1或0保存至目标寄存器 \\
    \hline \lstinline!array-length! & 将源寄存器指向的数组的长度取给目标寄存器 \\
    \hline \lstinline!new-instance! & 动态创建指定类型的一个新对象实例，将其引用值赋给目标寄存器 \\
    \hline \lstinline!new-array! & 动态创建指定类型的一个数组，其元素个数由源寄存器给出，将其引用值赋给目标寄存器 \\
    \hline \lstinline!filled-new-array! & 动态创建指定类型的一个数组，并以指定的值初始化，得到的结果必须使用\lstinline!move-result-object!来保存到特定寄存器，就像\lstinline!invoke!那样 \\
    \hline \lstinline!fill-array-data! & 以指定的数据填充一个数组 \\
    \hline \lstinline!throw! & 抛出一个指定的异常 \\
    \hline \lstinline!goto! & 无条件跳转至指定位置的指令 \\
    \hline \lstinline!packed-switch! & 测试寄存器的值，跳转到指定位置的指令\\
    \hline \lstinline!sparse-switch! & 测试寄存器的值，跳转到指定位置的指令，位置保存在排好序的值-偏移对中 \\
    \hline \lstinline!cmp! & 比较两个寄存器中的\lstinline!float!或\lstinline!double!或\lstinline!long!值，返回等于（0）、大于（-1）、小于（1）到目的寄存器 \\
    \hline \lstinline!if-!test & 例如\lstinline!if-eq!、\lstinline!if-lt!、\lstinline!if-ge!等，如果要比较的两个寄存器的值符合test条件，就跳转到指定偏移的指令 \\
    \hline \lstinline!if-!test\lstinline!z! & 与\lstinline!if-!test类似，但是是一个寄存器与0比较 \\
    \hline \lstinline!aget!,\lstinline!aput! & 根据索引读或者写一个数组（array）的指定元素 \\
    \hline \lstinline!iget!,\lstinline!iput! & 读或者写一个对象实例（instance）中的特定的域\\
    \hline \lstinline!sget!,\lstinline!sput! & 读或者写一个静态域（static field）中的特定的域\\
    \hline \lstinline!invoke-!kind & 函数调用，类型kind包括：\lstinline!virtual!虚方法、\lstinline!super!最近的父类的虚方法、\lstinline!direct!非静态直接方法、\lstinline!static!静态方法、\lstinline!interface!接口方法\\
    \hline unop & 单变量算术运算，例如\lstinline!neg-int!取整数的补码、\lstinline!long-to-float!将长整数转换为浮点数等等 \\
    \hline binop & 双变量算术运算，例如\lstinline!sub-int!整数相减，\lstinline!shr-long!长整数算术右移等等 \\
    \hline 
  \end{longtable}
\end{filecontents}
\LTXtable{\textwidth}{chapter-cn/LTXtable-dalvikopcode.tmp}

\section{smali语法}
由于在静态分析时smali反汇编得到Dalvik指令是目前最常用的一种方法，本节基于smali的语法对Dalvik指令集做进一步介绍。

本节和下一节的代码来自于lohan+在其博客\cite{url:androidcracking}上发表的两篇相关教程，他已经授权本文使用，在此表示感谢。

其中涉及大量关于Dalvik中数据类型的语法，请回顾第\ref{SubSec:dalvik_data_type}节。

先来看一个smali源文件，比较长，我们在后面逐一解释：
\lstinputlisting[language=smali, caption={smali源文件示例}]{code/example.smali}

第1行指明该文件所定义的类的名称\lstinline!com.packageName.example!，以及类属性\lstinline!public!；第2行指明该类的父类为\lstinline!java.lang.Object!；第3行指明该文件的文件名为example.java：
\lstinputlisting[language=smali, firstline=1, lastline=3, firstnumber=1]{code/example.smali}

第5和7行给出了类的两个域实例，一个是名为someInt的整型变量，一个是名为someBool的布尔型变量：
\lstinputlisting[language=smali, firstline=5, lastline=7, firstnumber=5]{code/example.smali}

接下来是该类的构造函数。先看一下这个函数的开始几行：
\lstinputlisting[language=smali, firstline=9, lastline=14, firstnumber=9]{code/example.smali}
第9行的\lstinline!.method!前缀说明下面是一个函数，\lstinline!public!说明其作用域，\lstinline!constructor!说明其是类的构造函数，使用了默认的\lstinline!<init>!作为供人阅读的函数名（显然，构造函数的真实函数名与类名相同）。接下来的\lstinline!(ZLjava/lang/String;I)V!需要注意，其中括号内是参数类型列表，多个参数直接写在一起，不使用任何符号分隔，括号后是返回值类型。在这个构造函数中，有三个参数，第一个参数\lstinline!Z!是布尔型；第二个参数\lstinline!Ljava/lang/String;!是Java中的字符（注意在以\lstinline!L!开头接对象全称的这种参数语法上，最后以分号结尾以划分与后面类型的界限）；第三个参数\lstinline!I!是整型。返回值是\lstinline!V!，即\lstinline!void!型。因此，这个构造函数的原型为：
\begin{lstlisting}[language=java, numbers=none]
public void example(boolean someBool, String exampleString, int someInt);
\end{lstlisting}
如果对这一部分还不理解，请回过头再看一下\ref{SubSec:dalvik_data_type}一节。

第10行指出这个函数需要使用六个寄存器来存储局部变量，在函数内部分别记为\lstinline!v0!、\lstinline!v1!到\lstinline!v5!。

第12到14行给出了三个参数的名称。注意有时候代码被混淆后，这部分信息将丢失。另外注意这里列举的顺序与函数实际参数顺序并不一致。

\lstinputlisting[language=smali, firstline=16, lastline=18, firstnumber=16]{code/example.smali}
第16行的\lstinline!.prologue!可以直接忽略。第17行的\lstinline!.line 10!标明行号，主要在调试时使用。

第18行出现了函数调用\lstinline!invode-direct!。其中，\lstinline!p0!是指参数0，即\lstinline!this!指针，这条语句调用了父类\lstinline!java.lang.Object!的构造函数，其参数为空，返回值为\lstinline!void!。

\lstinputlisting[language=smali, firstline=20, lastline=26, firstnumber=20]{code/example.smali}
第20行，将一个字符串的引用值赋给了局部寄存器\lstinline!v0!；第22行，将常量0xf（也就是十进制的15）赋给局部寄存器\lstinline!v0!，这会导致之前保存的字符串引用值丢失。

第24到26行，动态分配了一个\lstinline!java.lang.StringBuilder!对象，将其引用值赋给\lstinline!v1!，又将一个字符串的引用值赋给\lstinline!v2!，最后调用了\lstinline!v1!指向的\lstinline!StringBuilder!对象的构造函数，将\lstinline!v2!作为参数。注意第26行的\lstinline!invoke-direct!指令，其后的\lstinline!{v1, v2}!，第一个参数和第二个参数的不同作用。

\lstinputlisting[language=smali, firstline=28, lastline=29, firstnumber=28]{code/example.smali}
第28行，调用了\lstinline!v1!指向\lstinline!StringBuilder!对象的\lstinline!append(boolean)!方法，其参数使用\lstinline!p1!，即该函数的第一个实际参数\lstinline!boolean someBool!。注意这条指令的返回值是一个新的\lstinline!java.lang.StringBuilder!对象，在第29行，将这个返回值重新赋给了\lstinline!v1!。综上，第20到29行的源码应该是：

\begin{lstlisting}[language=java, numbers=none]
  StringBuilder a = new StringBuilder("the spice must flow");
  a = a.append(someBool);
\end{lstlisting}

我们跳过几行，直接看到第38行：

\lstinputlisting[language=smali, firstline=38, lastline=40, firstnumber=38]{code/example.smali}
可以看到，在39行，调用了\lstinline!android.util.Log!中的\lstinline!int d(String, String)!这个静态方法，注意这里使用了恰当的\lstinline!invoke-static!指令，并且其两个参数\lstinline!{v0, v1}!直接作为了\lstinline!d()!的两个参数，而不再像前面\lstinline!invoke-direct!那样第一个参数是方法所在对象的句柄。

再来看第50行：
\lstinputlisting[language=smali, firstline=50, lastline=50, firstnumber=50]{code/example.smali}
使用了\lstinline!iput-boolean!指令，前面已经介绍，\lstinline!iput!是对对象实例中域的操作，这里对象实例由\lstinline!p0!给出，因此这条指令是取出\lstinline!p1!的值，赋给\lstinline!p0!的名为\lstinline!someBool!的域。注意这里遇到了一个名称作用域的问题，当前函数的第一个参数叫\lstinline!someBool!，当前类实例有一个域也叫\lstinline!someBool!，由于使用的\lstinline!iput!，因此这里使用类实例的域。这一行代码等价于源码：
\begin{lstlisting}[language=java, numbers=none]
  this.someBool = someBool;
\end{lstlisting}

最后，在第61行，这个构造函数使用\lstinline!return-void!指令退出。到此，读者可以尝试自己阅读和分析第64到78行的\lstinline!someMethod!方法。

\section{常见源码结构}
无论是学习哪个指令集的反汇编，最好的方法都是自己动手将常见的代码结构写出来、编译好，再反汇编，将源码与反汇编结果对应起来看。lohan+再次为我们提供了这样的资源。

\subsection{构造函数和域}
\lstinputlisting[language=java, firstline=8, lastline=11, firstnumber=8, caption={构造函数、域的Java示例}]{code/example-structures.java}
\lstinputlisting[language=smali, firstline=5, lastline=18, firstnumber=5, caption={构造函数、域的smali示例}]{code/example-structures.smali}

Java中的静态变量，也就是域\lstinline!Count!，在smali中被\lstinline!.field!引导申明，说明其是一个静态私有\lstinline!int!型变量。

Java中类\lstinline!example!的构造函数，在smali中被\lstinline!.method!引导并被\lstinline!constructor!修饰，其方法名变成了\lstinline!<init>!。注意在第12行，该方法显式地调用了其父类的构造函数（同样以\lstinline!<init>!命名）。

在代码的第15行，\lstinline!sput!指令的作用是赋给一个域变量指定的值。注意域是类有关而与具体对象无关的，因此需要使用\lstinline!Lcom/lohan/crackme1/example;!来指出其归属关系。这也是为什么\lstinline!sput!需要单独作为一条指令。

\subsection{for结构}
\label{SubSec:dalvik-smali-for}
\lstinputlisting[language=java, firstline=13, lastline=17, firstnumber=13, caption={for结构的Java示例}]{code/example-structures.java}

\lstinputlisting[language=smali, firstline=62, lastline=97, firstnumber=62, caption={for结构的smali示例}]{code/example-structures.smali}

首先注意第70行和79行有两个标签\lstinline!:goto_1!和\lstinline!:cond_6!，这种标签用于标记代码位置，用于\lstinline!goto!跳转。

接下来看代码。在67行，定义了一个局部整形变量（在69行给出其名字信息\lstinline!i!），初始化为\lstinline!0!。在71行，将\lstinline!com.lohan.crackme1.example!类的域\lstinline!Counter!的值取到寄存器\lstinline!v1!。

然后在73行将\lstinline!i!与\lstinline!v1!作比较，使用的是\lstinline!if-lt!指令。注意这条指令的第三个操作数为标签\lstinline!:cond_6!。这条指令的执行效果是：如果\lstinline!i!比\lstinline!v1!小（less-than），就跳转到\lstinline!:cond_6!标记的位置；否则顺序执行，即执行\lstinline!return-void!返回。

当跳转到\lstinline!:cond_6!后，我们来看第80行到91行，它们对应的Java源码只有一行：
\begin{lstlisting}[language=java, numbers=none]
System.out.println("current val for loop: " + i);
\end{lstlisting}
但在smali中，使用了四个\lstinline!invoke!才完成。主要流程是：取\lstinline!java.lang.System!类的\lstinline!out!域，它是一个\lstinline!java.io.PrintStream!对象；创建一个\lstinline!java.lang.StringBuilder!对象；用固定的字符串初始化这个对象；用该对象的\lstinline!append!方法将\lstinline!i!加到字符串后面；用该对象的\lstinline!toString!方法转化为字符串对象；最后，调用\lstinline!out!的\lstinline!println!方法，打印出这个字符串。

由此可以看出，smali代码实际上已经到了Java VM opcode的细致程度，要精通smali，对JVM指令集的了解是需要的。

最后，在打印完字符串以后，在94行，通过\lstinline!add-int!指令，让\lstinline!i!加\lstinline!1!，然后跳转到\lstinline!:goto_1!的位置继续执行下一次循环。

这就是smali中\lstinline!for!循环的标准结构：两个位置标签，一个用于没有越界时跳过\lstinline!return!语句，一个用于加1后循环。

\subsection{switch结构}
\label{SubSec:dalvik-smali-switch}

\lstinputlisting[language=java, firstline=19, lastline=29, firstnumber=19, caption={switch结构的Java示例}]{code/example-structures.java}
\lstinputlisting[language=smali, firstline=99, lastline=158, firstnumber=99, caption={switch结构的smali示例}]{code/example-structures.smali}

smali中的\lstinline!switch!结构非常有特色。

首先看第109行，有一条\lstinline!sparse-switch!指令，从名字就可以看出其用于解析\lstinline!switch!结构。它有两个操作数，第一个是用于判断的值，在这个例子里是整形变量\lstinline!val!；第二个操作数名为\lstinline!:sswitch_data_2e!，这实质上是一个标签（即为一个地址）。

先看151行，这里有一段由\lstinline!.sparse-switch!和\lstinline!.end sparse-switch!修饰符界定的指令结构，其中每一行的格式类似于\lstinline!0x1 -> :sswitch_d!，其含义是：当\lstinline!switch!选择的值为\lstinline!0x1!时，跳转到\lstinline!:sswitch_d!这个标签指向的位置开始执行。

我们继续到这个跳转位置\lstinline!:sswitch_d!看，它位于121行，执行了该分支的指令后，执行了\lstinline!goto!，跳转到一个被标记为\lstinline!:goto_c!的地方直接返回。注意所有的分支最后都跳转到了这同一个位置返回。

最后，112到114这几行是做什么用的？对比一下Java源码可以看到，它是给\lstinline!default!分支使用的。

现在，让我们把整个代码合并到一起来看。在smali中，\lstinline!switch!结构的代码，首先使用\lstinline!sparse-switch!解析一个集中的跳转表，而这个跳转表会位于函数的最后；接下来根据跳转表，跳转到相应的分支执行；执行完成后，跳转到\lstinline!switch!后面的代码执行。

这种独特的设计，导致诸如\lstinline!dex2jar!等反编译工具在将Dalvik指令转成JVM指令，并由\lstinline!jd-gui!将JVM指令反编译为Java源码时，出现错误。例如，\lstinline!SwitchExample!函数就会被反编译如下：
\lstinputlisting[language=java, firstline=37, lastline=59, firstnumber=37, caption={dex2jar和jd-gui反汇编的switch结构}]{code/example-structures.dex2jar.java}
造成这种错误，就是没有解析\lstinline!sparse-switch!所指向的跳转表所致。

而如果使用\lstinline!ded!将Dalvik指令转成JVM指令，用\lstinline!soot!优化并反汇编，得到的结果要好很多（仍有细微错误）：
\lstinputlisting[language=java, firstline=48, lastline=75, firstnumber=48, caption={ded和soot反汇编的switch结构}]{code/example-structures.ded.java}

\subsection{try-catch结构}
\label{SubSec:dalvik-smali-trycatch}
\lstinputlisting[language=java, firstline=31, lastline=46, firstnumber=31, caption={try-catch结构的Java示例}]{code/example-structures.java}
\lstinputlisting[language=smali, firstline=160, lastline=262, firstnumber=160, caption={try-catch结构的smali示例}]{code/example-structures.smali}

Dalvik中，\lstinline!try-catch!结构也非常独特，其实现方式与\lstinline!switch!很相似。

首先看一下187和189行，如下：
\lstinputlisting[language=smali, firstline=187, lastline=189, firstnumber=187, caption={异常捕获结构片段}]{code/example-structures.smali}

这两行均由\lstinline!.catch!修饰符引导，后面有三个数据，第一个是要捕获的异常类型，例如\lstinline!Ljava/net/MalformedURLException;!；第二个是抛出异常的代码范围，在187行的\lstinline!{:try_start_4 .. :try_end_10}!，含义是\lstinline!:try_start_4!到\lstinline!:try_end_10!之间的代码抛出的异常在其接受范围内；第三个数据是处理异常的代码位置，例如\lstinline!:catch_11!。

如果分别看\lstinline!:try_start_4!和\lstinline!:try_end_10!之间的代码、\lstinline!:catch_11!开始的代码，会发现正好和源码一一对应上。这没有什么特别之处。

另一个细节是，在这个例子中，\lstinline!catch!处理完后，\lstinline!goto!到了195行函数返回。与\lstinline!switch!类似的原因，由于对\lstinline!.catch!修饰符的解析不足，导致\lstinline!dex2jar!和\lstinline!jd-gui!的组合在处理这样的结构时也会出错，如下所示：
\lstinputlisting[language=java, firstline=61, lastline=78, firstnumber=61, caption={dex2jar和jd-gui反汇编的try-catch结构}]{code/example-structures.dex2jar.java}

而使用\lstinline!ded!和\lstinline!soot!反编译得到的结果非常准确：
\lstinputlisting[language=java, firstline=77, lastline=95, firstnumber=77, caption={ded和soot反汇编的try-catch结构}]{code/example-structures.ded.java}

\subsection{数组}
\lstinputlisting[language=java, firstline=48, lastline=59, firstnumber=48, caption={数组的Java示例}]{code/example-structures.java}
\lstinputlisting[language=smali, firstline=20, lastline=60, firstnumber=20, caption={数组的smali示例}]{code/example-structures.smali}

首先看一下数组的动态分配。在第30行，使用了\lstinline!new-array!指令，它有三个操作数，第一个是用于保存结果的寄存器，第二个是用于提供数组大小的寄存器，第三个指出数组类型（注意不是数组元素的类型）。这里的类型是\lstinline![Ljava/lang/String;!，回忆\ref{SubSec:dalvik_data_type}节提到的数据类型描述方法，其中\lstinline![!表明它是一个数组，后面紧接元素类型。

在35行，要赋给数组元素的字符串值，直接使用\lstinline!const-string!生成了。注意与80到91行（参考前面的“for结构”）的区别，在那里，smali用\lstinline!StringBuilder!对象来初始化字符串，然后通过该对象的\lstinline!toString!方法得到字符串对象（请读者自己思考为什么这两处使用了不同的方法）。

在得到\lstinline!java.lang.String!对象后，使用了\lstinline!aput-object!将字符串对象赋给字符串数组中特定的元素。\lstinline!aput!中的\lstinline!a!是array的缩写，它有三个操作数，第一个是指向源对象的寄存器，第二个是保存了数组的寄存器，第三个是要操作的数组下标，即索引。可以在一条指令内直接对数组的某一元素直接操作，是JVM和Dalvik指令集的一个特点。

在43行，为了取出数组的两个元素进行对比，使用了\lstinline!aget-object!指令，它是与\lstinline!aput-object!对应的指令。

